﻿using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.Threading.Tasks;
using Autofac;
using Autofac.Extras.DynamicProxy;
using Hangfire;
using JX.Application;
using JX.Application.CommonService;
using JX.Core;
using JX.EF;
using JX.EF.Repository;
using JX.Infrastructure.Common;
using JX.Infrastructure.Framework.Authorize;
using JX.Infrastructure.Framework.Filter;
using JX.Infrastructure.Middleware;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using UEditor.Core;

namespace JXWebHost
{
	public class Startup
	{
		public IConfiguration Configuration { get; }
		/// <summary>
		/// 当前应用程序目录的绝对路径（例：D:\JXIPS\JXWebHost）
		/// </summary>
		public string m_ContentRoot = string.Empty;
		/// <summary>
		/// 当前应用程序的静态文件目录的绝对路径（例：D:\JXIPS\JXWebHost\wwwroot）
		/// </summary>
		public string m_WebRootPath = string.Empty;

		public Startup(IConfiguration configuration, IWebHostEnvironment env)
		{
			Configuration = configuration;
			m_ContentRoot = env.ContentRootPath;
			m_WebRootPath = env.WebRootPath;
		}

		// This method gets called by the runtime. Use this method to add services to the container.
		public void ConfigureServices(IServiceCollection services)
		{
			var databaseConfig = Configuration.GetSection("DatabaseConfig").Get<DatabaseConfig>();
			////注入DbContext
			//services.AddDbContext<ApplicationDbContext>(options =>
			//{
			//	options.UseSqlServer(databaseConfig.WriteDB,options=>options.CommandTimeout(90));

			//}, ServiceLifetime.Scoped);
			////注入Uow依赖
			//services.AddScoped<IUnitOfWork, UnitOfWork>();
			////注入DbContext工厂
			//services.AddScoped<IDbContextFactory, DbContextFactory>();
			//添加对Microsoft.AspNetCore.Http.HttpContext对象的扩展
			services.AddMyHttpContextAccessor();
			//添加GZIP压缩服务
			services.AddResponseCompression();
			services.AddSession();
			//解决MVC输出中文被编码的问题
			services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));

			#region 身份验证与授权
			//添加身份验证
			services.AddAuthentication(o =>
			{
				o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
				o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
				o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
			})
			.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
			{
				options.LoginPath = new PathString("/User/Login");
				options.LogoutPath = new PathString("/User/Logout");
				options.AccessDeniedPath = new PathString("/User/Forbidden");
				//options.Cookie.Domain = "domain.com";//设置同一个根域名，可以跨不同的二级域名
				options.Cookie.Path = "/";
				options.Cookie.HttpOnly = true;
				options.Cookie.SameSite = SameSiteMode.Lax;
				//表示创建的Cookie是否应该被限制为HTTPS，HTTP或HTTPS，或与请求相同的协议。
				options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
				//服务端变化反馈，用于数据库中变更了会员状态时，将客户端的Cookies失效
				options.Events = new CookieAuthenticationEvents
				{
					OnValidatePrincipal = UserStatusValidator.ValidateAsync
				};
			})
			.AddCookie(AdminAuthorizeAttribute.AdminAuthenticationScheme, options =>
			{
				options.LoginPath = new PathString("/Admin/Home/Login");
				options.LogoutPath = new PathString("/Admin/Home/Logout");
				options.AccessDeniedPath = new PathString("/Admin/Home/Forbidden");
				//options.Cookie.Domain = "domain.com";//设置同一个根域名，可以跨不同的二级域名
				options.Cookie.Path = "/";
				options.Cookie.HttpOnly = true;
				options.Cookie.SameSite = SameSiteMode.Lax;
				//表示创建的Cookie是否应该被限制为HTTPS，HTTP或HTTPS，或与请求相同的协议。
				options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
				//服务端变化反馈，用于数据库中变更了管理员状态时，将客户端的Cookies失效
				options.Events = new CookieAuthenticationEvents
				{
					OnValidatePrincipal = AdminStatusValidator.ValidateAsync
				};
			});

			//用于分布式共享Cookie，部署于不同服务器
			services.AddDataProtection()
				.SetApplicationName("cookieshare")//多个应用程序之间共享，必须设置成同一个名字
				.AddKeyManagementOptions(options =>
				{
					options.XmlRepository = new XmlRepository(Path.Combine(m_ContentRoot, "Config", "ShareKeys.xml"));
				});
			//添加授权
			services.AddAuthorization();
			#endregion
			#region IdentityServer4配置
			////确保不更改来自授权服务器的任何返回声明
			//JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
			//services.AddAuthentication(options =>
			//{
			//	options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
			//	options.DefaultChallengeScheme = "oidc";//OpenIdConnectDefaults.AuthenticationScheme;
			//})
			//.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
			//.AddOpenIdConnect("oidc", options =>
			//{
			//	options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
			//	options.Authority = "http://localhost:9000";//验证服务器地址
			//	options.RequireHttpsMetadata = false;//是否使用https
			//	options.ClientId = "JXWebMvc";
			//	options.ClientSecret = "JXWebMvc";
			//	options.ResponseType = "code";
			//	//options.ResponseType = "id_token token"; // allow to return access token
			//	options.SaveTokens = true;

			//	//options.Scope.Clear();
			//	//options.Scope.Add(OidcConstants.StandardScopes.OpenId);
			//	//options.Scope.Add(OidcConstants.StandardScopes.Profile);
			//	//options.Scope.Add(OidcConstants.StandardScopes.OfflineAccess);
			//});
			#endregion

			//处理跨站请求伪造
			services.AddAntiforgery(options =>
			{
				// Set Cookie properties using CookieBuilder properties†.
				options.FormFieldName = "AntiforgeryFieldname";
				options.HeaderName = "X-CSRF-TOKEN-JXWebHost";
				options.SuppressXFrameOptionsHeader = false;
			});

			//添加Hangfire服务，用于定时任务
			//services.AddHangfire(x => x.UseSqlServerStorage(databaseConfig.WriteDB));
			//services.AddHangfireServer();
			//添加UEditor编辑器
			services.AddUEditorService("ueditor.json", true, m_WebRootPath);
			
			services.AddControllersWithViews(options =>
			{
				options.Filters.Add<ModelStateActionFilter>();
				options.Filters.Add<CustomExceptionFilterAttribute>();
			})
			.AddJsonOptions(options =>
			{
				//json返回格式使用PascalCase命名方式，即输出的JSON全部采用首字母大写的命名格式
				options.JsonSerializerOptions.PropertyNamingPolicy = null;
				//解决输出JSON时的中文被编码问题
				options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
			})
			.AddControllersAsServices();//配置Controller全部由Autofac创建,属性注入必须加上这个
		}

		//集成Autofac
		public void ConfigureContainer(ContainerBuilder builder)
		{
			//先注册拦截器
			builder.RegisterType<CommonAOP>();

			// 首先注册 options，供 DbContext 服务初始化使用
			var databaseConfig = Configuration.GetSection("DatabaseConfig").Get<DatabaseConfig>();
			builder.Register(c =>
			{
				var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
				optionsBuilder.UseSqlServer(databaseConfig.WriteDB, options => options.CommandTimeout(90));
				return optionsBuilder.Options;
			}).InstancePerLifetimeScope();
			// 注册 DbContext
			builder.RegisterType<ApplicationDbContext>().AsSelf().InstancePerLifetimeScope().PropertiesAutowired();
			//注入Uow依赖
			builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerLifetimeScope().PropertiesAutowired();
			//注入DbContext工厂
			builder.RegisterType<DbContextFactory>().As<IDbContextFactory>().InstancePerLifetimeScope().PropertiesAutowired();
			
			//程序集范围自动注入
			Assembly service = Assembly.Load("JX.Application");
			Assembly repository = Assembly.Load("JX.EF");
			Assembly interfaces = Assembly.Load("JX.Core");
			builder.RegisterAssemblyTypes(repository, interfaces)
				 .Where(t => t.Name.EndsWith("Repository"))
				 .AsImplementedInterfaces().InstancePerLifetimeScope().PropertiesAutowired()
				 .EnableInterfaceInterceptors();// 允许在接口上使用拦截器
			builder.RegisterAssemblyTypes(service, repository)
				 .Where(t => t.Name.EndsWith("ServiceApp"))
				 .AsImplementedInterfaces().InstancePerLifetimeScope().PropertiesAutowired()
				 .EnableInterfaceInterceptors();// 允许在接口上使用拦截器

			//在控制器中使用属性依赖注入，其中注入属性必须标注为public
			var controllerBaseType = typeof(ControllerBase);
			builder.RegisterAssemblyTypes(typeof(Program).Assembly)
				.Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
				.PropertiesAutowired()// 允许属性注入
				.EnableClassInterceptors();// 允许在Controller类上使用拦截器
		}

		// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
		public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
		{
			//使网站支持中文编码
			Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
			//使用GZIP压缩
			app.UseResponseCompression();

			if (env.IsDevelopment())
			{
				app.UseDeveloperExceptionPage();
			}
			else
			{
				app.UseExceptionHandler("/Home/Error");
			}

			//添加依赖注入的自定义扩展
			app.UseExtensionsDI();
			//添加对HttpContext的扩展
			app.UseStaticMyHttpContext();
			app.UseStaticFiles();
			app.UseSession();
			app.UseHotlinkingPreventionMiddleware();
			
			#region 启用Hangfire仪表盘
			//app.UseHangfireDashboard();
			//app.Map("/TimeJob1", r =>
			//{
			//	r.Run(context =>
			//	{
			//		//任务每分钟执行一次
			//		RecurringJob.AddOrUpdate(() => Console.WriteLine($"ASP.NET Core LineZero"), Cron.Minutely());
			//		return context.Response.WriteAsync("ok");
			//	});
			//});
			//app.Map("/TimeJob2", r =>
			//{
			//	r.Run(context =>
			//	{
			//		//任务执行一次
			//		BackgroundJob.Enqueue(() => Console.WriteLine($"ASP.NET Core One Start LineZero{DateTime.Now}"));
			//		return context.Response.WriteAsync("ok");
			//	});
			//});
			//app.Map("/TimeJob3", r =>
			//{
			//	r.Run(context =>
			//	{
			//		//任务延时两分钟执行
			//		BackgroundJob.Schedule(() => Console.WriteLine($"ASP.NET Core await LineZero{DateTime.Now}"), TimeSpan.FromMinutes(2));
			//		return context.Response.WriteAsync("ok");
			//	});
			//});
			#endregion

			app.UseRouting();

			//鉴权，检测有没有登录，登录的是谁，赋值给User
			app.UseAuthentication();
			//授权，检测权限
			app.UseAuthorization();

			app.UseEndpoints(endpoints =>
			{
				//Area在路由时，匹配不到有/admin/home/index/1这样的URL，原因未知
				endpoints.MapAreaControllerRoute(
					name: "areas",
					areaName: "areas",
					pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
				
				endpoints.MapControllerRoute(
					name: "default",
					pattern: "{controller=Home}/{action=Index}/{id?}");
			});
		}
	}
}
